// Texas A&M University          //
// cpsc321 Computer Architecture //

// Top Level Architecture Model //

`include "Clock.v"
`include "Controller.v"
`include "Adder32bit.v"
`include "ALU_behav.v"
`include "InstrMem.v"
`include "DataMem.v"
`include "LShift2.v"
`include "Mux.v"
`include "RegFile.v"
`include "Extender.v"

`include "Fetch_Decode.v"
`include "Decode_Execute.v"
`include "Execute_Mem.v"
`include "Mem_WriteBack.v"

`include "Hazard_Unit.v"
`include "Forward_Unit.v"


/*-------------------------- CPU -------------------------------
//
// Input Ports
// -----------
// clock - the system clock (m555 timer).
//
// reset - when asserted by the test module, forces the processor to 
//         perform a "reset" operation.
//   
// startPC - during a reset, becomes the new contents of the program counter
//	     (starting address of test program).
//--------------------------------------------------------------
*/
module Initialize(Reset,Flush0,Flush1,Flush2);

  input Reset;

  output Flush0,Flush1,Flush2;

  reg Flush0,Flush1,Flush2;

  always@(!Reset)
    begin
      Flush0  <= 1;
      Flush1  <= 1;
      Flush2 <= 1;

      #10 Flush0  <= 0;
      #10 Flush1  <= 0;
      #10 Flush2 <= 0;
    end

endmodule

module PipelineProc(CLK, Reset_L, StartPC);
  input CLK, Reset_L;
  input [31:0] StartPC;

	reg [31:0] PC;
  wire [31:0] Instruction;

//// OUT OF PLACE DECLARATIONS ////
  wire C_RegWrite2, C_MemRead2, C_RegWrite3, NoOp, C_DoBranch2;
  wire [1:0] C_Jump2;
  wire [4:0] Rs1, Rt1, RwIn2, RwIn3;
  wire [31:0] Instruction0, JumpAddr2, BusA2, ALUOut2, AdderBOut2, ALUOut3;


/********* Reset / Initialize Instruction ****************************/
  wire Flush0,Flush1,Flush2;
  //Initialize InitPipeline(Reset_L,Flush0,Flush1,Flush2);
  always@(!Reset_L)
		begin
			PC = StartPC;
		end
/********************************************************************/


/********* Controller ***********************************************/
  // Controller Wire declaration
  wire C_RegWrite0, C_MemRead0, C_MemWrite0, C_ALUSrc0, C_ExtOpt0;
  wire [1:0] C_RegWriteSrc0, C_Jump0, C_Branch0, C_RegDst0;
  wire [5:0] C_ALUCtr0;
  // Controller Initiation
  Controller ProcControl(Instruction0,C_RegDst0,C_ALUSrc0,C_RegWriteSrc0,C_RegWrite0,C_MemRead0,C_MemWrite0,C_Branch0,C_Jump0,C_ALUCtr0,C_ExtOpt0);
/********************************************************************/


/********* Pre-Instruction Fetch / PC Handling **********************/
  // Adder A Wire declaration
  wire [31:0] AdderAIn1, AdderAOut;
  assign AdderAIn1 = 32'd4;
  // Adder A Initiation
  Adder32bit AdderA(AdderAOut,,AdderAIn1,PC,1'b0);

  // Branch MUX Wire declaration
  wire [31:0] BranchMUXOut;
  // Branch MUX Initiation
  Mux32_2to1 BranchMUX(AdderAOut,AdderBOut2,C_DoBranch2,BranchMUXOut);

  // Jump MUX Wire declartion
  wire [31:0] JumpMUXOut;
  // Jump MUX Initiation (Alias: PC Source MUX)
  Mux32_3to1 JumpMUX(BranchMUXOut,JumpAddr2,BusA2,C_Jump2,JumpMUXOut);

  // PC Register Wire declaration
  wire [31:0] PCIn;
  assign PCIn = JumpMUXOut;
  // PC Register Initiation
  always@(negedge CLK)
    PC = PCIn;

  // Instruction Memory Initiation
  InstrMem InstructionMemory(PC,Instruction);
/********************************************************************/


/********* Instruction Fetch / Instruction Decode Handling **********/
  // IF/ID Wire declaration
  wire [31:0] PC0,AdderAOut0;
  assign Flush0 = C_DoBranch2 || C_Jump2;
  // IF/ID Register Initiation
  Fetch_Decode _IF_ID (CLK,PC,PC0,Instruction,Instruction0,AdderAOut,AdderAOut0,Flush0);
/********************************************************************/


/********* Stage 0 **************************************************/

  // Register Wire declaration
  wire [4:0] RaIn,RbIn;
  wire [31:0] BusW,BusA0,BusB0;
  assign RaIn = Instruction0[25:21];
  assign RbIn = Instruction0[20:16];
  // Register File Initiation
  RegFile Reg32(C_RegWrite3,Reset_L,RwIn3,RaIn,RbIn,BusW,BusA0,BusB0,CLK);

  // Jump structures wire declaration
  wire [25:0] PartJumpAddr_26b;
  wire [31:0] JumpAddr0, JumpShiftOut;
  wire [3:0] PCFirst4;
  assign PCFirst4 = PC0[31:28];
  assign PartJumpAddr_26b = Instruction0[25:0];
  // Jump structures Initiation
  LShift2 JumpAddrShifter({{6'b000000},{PartJumpAddr_26b}},JumpShiftOut);
  assign JumpAddr0 = {{PCFirst4},{JumpShiftOut}};

  // Hazard Unit Wire declaration
  // Hazard Unit Initiation
  Hazard_Unit Hazards(C_MemRead1, Rs1, Rt1, RaIn, RbIn, NoOp);
/********************************************************************/


/********* Instruction Decode / Execution  Handling *****************/
  // ID/EXE Wire declaration
  wire C_RegWrite1, C_MemRead1, C_MemWrite1, C_ALUSrc1, C_ExtOpt1;
  wire [1:0] C_RegWriteSrc1, C_Jump1, C_Branch1, C_RegDst1;
  wire [4:0] Instruction15_11, Instruction20_16, Instruction25_21;
  wire [5:0] C_ALUCtr1; 
  wire [31:0] PC1, AdderAOut1, JumpAddr1, BusA1, BusB1;
  wire [15:0] Instruction15_0;
  assign Flush1 = NoOp || C_DoBranch2 || C_Jump2;
  // ID/EXE Register Initiation
  Decode_Execute _ID_EXE(CLK,
                            C_RegWrite0,C_RegWrite1, 
                            C_RegWriteSrc0, C_RegWriteSrc1, 
                            C_MemRead0, C_MemRead1,
                            C_MemWrite0, C_MemWrite1,
                            C_Jump0, C_Jump1,
                            C_Branch0, C_Branch1,
                            C_RegDst0, C_RegDst1,
                            C_ALUSrc0, C_ALUSrc1,
                            C_ALUCtr0, C_ALUCtr1, 
                            C_ExtOpt0, C_ExtOpt1,
                            AdderAOut0, AdderAOut1,
                            JumpAddr0, JumpAddr1,
                            BusA0, BusA1,
                            BusB0, BusB1,
                            Instruction0,
                            Instruction15_0,
                            Instruction25_21,
                            Instruction20_16,
                            Instruction15_11,
                            Flush1);
/********************************************************************/


/********* Stage 1 **************************************************/
  // Sign Extender Wire declaration
  wire [31:0] ExtOut1;
  // Sign Extender Initiation
  Extender SignExtender(C_ExtOpt1,Instruction15_0,ExtOut1);

  // Immediate Branch Shifter Wire declaration
  wire [31:0] BranchShiftOut1;
  // Immediate Branch Shifter Initiatio
  LShift2 BranchShifter(ExtOut1,BranchShiftOut1);

  // Branch Adder Wire declaration
  wire [31:0] AdderBOut1;
  // Branch Adder Initiation
  Adder32bit AdderB(AdderBOut1,,AdderAOut1,BranchShiftOut1,1'b0);

  // ForwardA MUX Wire declaration
  wire [1:0] C_ForwardA;
  wire [31:0] ALUSrcA;
  // ForwardA MUX Initiation
  Mux32_3to1 ForwardAMUX(BusA1,BusW,ALUOut2,C_ForwardA,ALUSrcA);

  // ForwardB MUX Wire declaration
  wire [1:0] C_ForwardB;
  wire [31:0] ForwardBOut;
  // ForwardB MUX Initiation
  Mux32_3to1 ForwardBMUX(BusB1,BusW,ALUOut2,C_ForwardB,ForwardBOut);

  // ALU SourceB MUX Wire declaration
  wire [31:0] ALUSrcB;
  // ALU SourceB MUX Initiation
  Mux32_2to1 ALUSrcBMUX(ForwardBOut,ExtOut1,C_ALUSrc1,ALUSrcB);

  // ALU Wire declaration
  wire C_Zero1;
  wire [4:0] ShiftAmt;
  wire [31:0] ALUOut1;
  assign ShiftAmt = Instruction15_0[10:6];
  // ALU Initiation
  ALU_behav ALUMain(ALUSrcA,ALUSrcB,C_ALUCtr1,ShiftAmt,ALUOut1,C_Zero1);

  // Register Destination MUX Wire declaration
  wire [4:0] Ra,Rd1,RwIn1;
  assign Ra = 5'd31;                          // Return register $ra
  assign Rd1 = Instruction15_11;
  assign Rt1 = Instruction20_16;
  assign Rs1 = Instruction25_21;
  // Register Destination MUX Initiation
  Mux5_3to1 RegisterDstMUX(Rt1,Rd1,Ra,C_RegDst1,RwIn1);

  // Forward Unit Wire Declaration
  // Forward Unit Initiation
  Forward_Unit Forwarding(C_MemRead2, C_ForwardA, C_ForwardB, C_RegWrite3, C_RegWrite2, RwIn3, RwIn2, Rs1, Rt1, RaIn, RbIn);
/********************************************************************/


/********* Execution / Memory  Handling *****************************/
  // EX/MEM Wire declaration
  wire C_MemWrite2, C_Zero2;
  wire [1:0] C_RegWriteSrc2, C_Branch2;
  wire [31:0] AdderAOut2, BusB2;
  assign Flush2 = C_DoBranch2 || C_Jump2;
  // EX/MEM Register Initiation
  Execute_Mem _EXE_MEM(CLK,
                           C_RegWrite1,C_RegWrite2, 
                           C_RegWriteSrc1, C_RegWriteSrc2, 
                           C_MemRead1, C_MemRead2,
                           C_MemWrite1, C_MemWrite2,
                           C_Jump1, C_Jump2,
                           C_Branch1, C_Branch2,
                           C_Zero1, C_Zero2, 
                           ALUOut1, ALUOut2,
                           AdderAOut1, AdderAOut2,
                           JumpAddr1, JumpAddr2,
                           AdderBOut1, AdderBOut2,
                           ALUSrcA, BusA2,
                           ForwardBOut, BusB2,
                           RwIn1, RwIn2,
                           Flush2);
/********************************************************************/


/********* Stage 2 **************************************************/
  // Branch Handling Wire declaration
  wire BranchEqual, BranchNotEqual;
  // Branch Handling Initiation
  and(BranchEqual,C_Branch2[0],C_Zero2);
  and(BranchNotEqual,C_Branch2[1],~C_Zero2);
  or(C_DoBranch2,BranchEqual,BranchNotEqual);

  // Data Memory Wire declaration
  wire [31:0] MemoryOut2;
  // Data Memory Initiation
  DataMem DataMemory(ALUOut2,CLK,C_MemWrite2,BusB2,MemoryOut2);
/********************************************************************/


/********* Memory / Write Back Handling *****************************/
  // MEM/WB Wire declaration
  wire C_MemWrite3;
  wire [1:0] C_RegWriteSrc3; 
  wire [31:0] AdderAOut3, MemoryOut3, BusB3;
  // MEM/WB Register Initiation
  Mem_WriteBack _MEM_WB(CLK,
                           C_RegWrite2,C_RegWrite3, 
                           C_RegWriteSrc2, C_RegWriteSrc3, 
                           ALUOut2, ALUOut3,
                           AdderAOut2, AdderAOut3,
                           MemoryOut2, MemoryOut3,
                           BusB2, BusB3,
                           RwIn2, RwIn3);
/********************************************************************/


/********* Stage 3 **************************************************/
  // Bus W Source MUX Wire declaration

  // Bus W Source MUX Initiation
  Mux32_3to1 BusWMUX(ALUOut3,MemoryOut3,AdderAOut3,C_RegWriteSrc3,BusW);
/********************************************************************/


/********* Debugging / User Feedback ********************************/
	always @(negedge CLK)
		begin
      $display("-------------------------------------------------------");
      $display("Pre-Stage");
      $display("\tPC Handling:");
			$display($time, " BranchMUXOut=%h, JumpMUXOut=%h, PC=%h,",BranchMUXOut,JumpMUXOut,PC);
      if(C_DoBranch2)
        begin
          $display("\nBranch Occurred.\n");
        end
      if(C_Jump2)
        begin
          $display("\nJump Occurred.\n");
        end
      $display("\tAdder A Out: %h",AdderAOut);
      $display("Instruction Memory");
      $display($time," MemAddr=%d=%h ArrayInd=%d, Instruction=%h",PC,PC,PC/4,Instruction); 
			
      $display("Stage 0:");
      $display("\tInstruction0: %h", Instruction0);
      $display("\tRegister File:");
    	$display($time," Rs=%d, Rt=%d, Rw=%d, busW=h=%d",RaIn,RbIn,RwIn3,BusW,BusW);
      $display("\tJump Address0: %d", JumpAddr0);
      $display("\tRegWriteSrc0: %d", C_RegWriteSrc0);
      $display("\tC_ALUSrc0: %d", C_ALUSrc0);
      $display("\tC_ExtOpt0: %d", C_ExtOpt0);

      $display("Stage 1:");
      $display("\tJump Address1: %d", JumpAddr1);
      $display("\tAdder B Out: %h", AdderBOut1);
      $display("\tForwarding MUX Options:");
    	$display($time," BusW=%d, ALUOut2=%d,\n\t\t BusA1=%d, C_ForwardA=%d, ALUSrcA=%d",BusW,ALUOut2,BusA1,C_ForwardA,ALUSrcA);
    	$display($time," BusB1=%d, C_ForwardB=%d",BusB1,C_ForwardB);
      $display("\tALU Source B:");
    	$display($time," ExtOut1=%d, C_ALUSrc1=%d, ALUSrcB=%d",ExtOut1,C_ALUSrc1,ALUSrcB);
      $display("\tALU Out: %d, ALUCtr=%d", ALUOut1,C_ALUCtr1);
      $display("\tRegister Destination Mux:");
    	$display($time," Rs=%d, Rt=%d, Rd=%d, $Ra=%d, C_RegDst1=%d, RwIn1=%d",Rs1,Rt1,Rd1,Ra,C_RegDst1,RwIn1);
      $display("\tC_RegWrite1: %d, RegWriteSrc1: %d", C_RegWrite1,C_RegWriteSrc1);
      $display("\tC_ExtOpt1: %d", C_ExtOpt1);

      $display("Stage 2:");
      $display("\tJumping:");
      $display($time," JumpAddr2=%d, C_Jump2=%d", JumpAddr2, C_Jump2);
      $display("\tBranching:");
      $display($time," C_Zero2=%d, C_DoBranch2=%d",C_Zero2,C_DoBranch2);
      $display("\tMemory Unit:");
    	$display($time," C_MemWrite2=%d, ALUOut2(Addr)=%d, BusB2(WriteData)=%d, MemoryOut2=%d",C_MemWrite2,ALUOut2,BusB2,MemoryOut2);
      $display("\tRwIn2: %d",RwIn2);
      $display("\tC_RegWrite2: %d, RegWriteSrc2: %d", C_RegWrite2,C_RegWriteSrc2);
      
      $display("Stage 3:");
      $display("\tBusW Mux:");
    	$display($time," ALUOut3=%d, MemoryOut3=%d, AdderAOut3=%d, C_RegWriteSrc3=%d",ALUOut3,MemoryOut3,AdderAOut3,C_RegWriteSrc3);
      $display("\tC_RegWrite3: %d, RwIn3: %d, BusW=%d",C_RegWrite3,RwIn3, BusW);
 //     $display("\tC_RegWrite3=%d, RwIn3: %d",C_RegWrite3,RwIn3);
      $display("-------------------------------------------------------");
		end
initial
 //   $monitor($time, " CLK=%d, RwIn1=%d, RwIn2=%d, RwIn3=%d",CLK,RwIn1,RwIn2,RwIn3);
      $monitor($time, " NoOp=%d, C_DoBranch2=%d, C_Jump2=%d, Flush0=%d, Flush1=%d, Flush2=%d.",NoOp,C_DoBranch2,C_Jump2,Flush0,Flush1,Flush2);
   //end 
/*
  always@(PC)
		begin
      #0 $display("Instruction Memory");
      #0 $display($time," MemAddr=%d=%h ArrayInd=%d, Instruction=%h",PC,PC,PC/4,Instruction); 
		end
 */   

	// Display when a register is written.
  always@(posedge CLK)
		begin
      if(C_RegWrite3)
        begin
         $display("Register Write:");
         $display($time," C_RegWrite3=%d, RwIn3=%d, BusW=%h=%d",C_RegWrite3, RwIn3,BusW,BusW);
        end
		end	
/********************************************************************/


endmodule
// END PROCESSOR MODULE

// Module that resets processor and passes 1st instruction address.  
module TestCPU(Reset, StartPC);
   output Reset;
   output [31:0] StartPC;

   reg Reset;
   reg [31:0] StartPC;
   
   initial begin
			$display("Program is starting:");
      // Reset Processor and Start at 1st Instruction
      Reset = 0;  StartPC = 0 * 4;
      // Un-reset after 1 clock cycle
      #101 Reset = 1; 

      // PROGRAM EXECUTION HAPPENS HERE

      // End of Program Execution
      #5000 Reset = 0;
      
      $finish;
   end
endmodule 

// TopProcessor //
// 
// Initializes top-level components that are connected.
//   -Processor
//   -Clock
//   -Program Instructions
module TopProcessor;
   wire Reset, CLK;
   wire [31:0] StartPC;
   
   m555 system_clock(CLK);
   PipelineProc Processor(CLK, Reset, StartPC);
   TestCPU tCPU(Reset, StartPC); 

endmodule 
